#include "GraphDisplay.h"
#include "BitmapImage.h"
#include <QtGui/QPainter.h>
#include <QtOpenGL/QGLWidget>
#include <QtGui/qimage.h>
#include <QtGui/qpixmap.h>
#include <QtGui/Qpaintevent>
#include <QtGui/QMouseEvent>

#include <algorithm>

using namespace std;
using namespace GUI;

namespace GUI
{
	class WidgetBase
	{
	public:
		WidgetBase(GraphDisplay&);
		virtual ~WidgetBase();

	protected:
		void SetWidget(QWidget*);
		void OnPaintEvent(QPaintEvent*);
		void OnMousePressEvent(QMouseEvent*);
	private:
		GraphDisplay&	mrGraphDisplay;
		QWidget*		mpWidget;
	};

	class GLWidget : public QGLWidget, private WidgetBase
	{
	public:
		GLWidget(GraphDisplay& d, const QGLFormat& f, QWidget* p)
			:QGLWidget(f, p), WidgetBase(d){ SetWidget(p); }

	protected:
		void paintEvent(QPaintEvent* e){ WidgetBase::OnPaintEvent(e); }
		void mousePressEvent(QMouseEvent* e){ WidgetBase::OnMousePressEvent(e); }
	};

	class Widget : public QWidget, private WidgetBase
	{
	public:
		Widget(GraphDisplay& d, QWidget* p)
			:QWidget(p), WidgetBase(d){ SetWidget(p); }

	protected:
		void paintEvent(QPaintEvent* e)
		{WidgetBase::OnPaintEvent(e); }
		void mousePressEvent(QMouseEvent* e)
		{ WidgetBase::OnMousePressEvent(e); }
	};
} // namespace GUI

GraphDisplay::GraphDisplay()
	:mColor(RGBColor::Gray)
{
	mContext.rect.left = 0;
	mContext.rect.right = 0;
	mContext.rect.top = 0;
	mContext.rect.bottom = 0;

	mContext.handle.device = NULL;
	mContext.handle.painter = NULL;
	mContext.handle.glContext = NULL;

	mpWidget = NULL;
	mUsingGL = false;
	mOffscreenBmp = NULL;
}

GraphDisplay::~GraphDisplay()
{
	delete mpWidget;
	ClearOffscreenBuffer();
	DeleteObjects();
}

const GraphDisplay& GraphDisplay::Update() const
{
	if(mpWidget)
		mpWidget->repaint(mInvalidRegion.translated(-mpWidget->pos()));
	return *this;
}

GraphDisplay& GraphDisplay::Add(GraphObject* obj)
{
	mObjects.insert(obj);
	return *this;
}

GraphDisplay& GraphDisplay::Remove(GraphObject* obj)
{
	obj->Invalidate();
	mObjects.erase(obj);
	mObjectsClicked.remove(obj);
	if(obj->NeedsGL())
		Change();

	return *this;
}

void GraphDisplay::Change()
{
	bool useGL = false;
	for(SetOfGraphObjects::iterator i = mObjects.begin(); i != mObjects.end(); ++i)
		useGL |= (*i)->NeedsGL();
	if(!useGL && mUsingGL || useGL && !mUsingGL)
	{
		delete mpWidget;
		mpWidget = NULL;
		mContext.handle.glContext = NULL;
	}
	mUsingGL = useGL;

	QWidget* pParent = dynamic_cast<QWidget*>(mContext.handle.device);
	if(mpWidget && mpWidget->parent() != pParent)
	{
		delete mpWidget;
		mpWidget = NULL;
		mContext.handle.glContext = NULL;
	}
	if(pParent && !mpWidget)
	{
		if(useGL)
		{
			QGLFormat format = QGLFormat::defaultFormat();
			format.setDepth(true);
			format.setDepthBufferSize(16);
			format.setOverlay(true);
			format.setSwapInterval(0);	// disable VSync
			GLWidget* pWidget = new GLWidget(*this, format, pParent);
			if(pWidget->isValid())
				mpWidget = pWidget;
			else
				delete pWidget;
		}else
		{
			mpWidget = new Widget(*this, pParent);
		}
	}
	if(mUsingGL && !mContext.handle.glContext)
	{
		mContext.handle.glContext = dynamic_cast<QGLWidget*>(mpWidget);
	}

	if(mpWidget)
	{
		mpWidget->setGeometry(mContext.rect.left,
							  mContext.rect.top,
							  mContext.rect.right - mContext.rect.left,
							  mContext.rect.bottom - mContext.rect.top);
	}

	ClearOffscreenBuffer();
	Invalidate();
	for(SetOfGraphObjects::iterator i = mObjects.begin(); i!=mObjects.end(); ++i)
		(*i)->Change();
}

void GraphDisplay::Paint(const void* inRegionHandle)
{
	int left = static_cast<int>(mContext.rect.left),
		top  = static_cast<int>(mContext.rect.top),
		width = static_cast<int>(mContext.rect.right - mContext.rect.left),
		height = static_cast<int>(mContext.rect.bottom-mContext.rect.top);
	if(!mpWidget && mOffscreenBmp == NULL)
		mOffscreenBmp = new QPixmap(width, height);
	if(mOffscreenBmp != NULL && mColor == RGBColor::NullColor)
		mOffscreenBmp->fill(Qt::transparent);
	QPainter* pPainter = mpWidget ? new QPainter(mpWidget) : new QPainter(mOffscreenBmp);
	pPainter->translate(-left, -top);
	const QRegion* pInputRegion = reinterpret_cast<const QRegion*>(inRegionHandle);
	if(pInputRegion)
		mInvalidRegion += *pInputRegion;
	pPainter->setClipRegion(mInvalidRegion);
	// Create a rect for background color
	if(mColor != RGBColor::NullColor)
		pPainter->fillRect(QRect(left, top,width, height),
						   QColor(mColor.R(), mColor.G(), mColor.B()));
	mContext.handle.painter = pPainter;

	vector<GraphObject*> objs(mObjects.begin(), mObjects.end());
	sort(objs.begin(), objs.end(), GraphObject::CompareByZOrder());
	for(vector<GraphObject*>::iterator i = objs.begin(); i!=objs.end(); i++)
		(*i)->Paint();

	pPainter->end();
	mContext.handle.painter = NULL;
	delete pPainter;
	mInvalidRegion = QRegion();
}

void GraphDisplay::Click(int x, int y)
{
	int width = mContext.rect.right - mContext.rect.left,
		height = mContext.rect.bottom - mContext.rect.top;
	GUI::Point p = {
		(x - mContext.rect.left)/width,
		(y - mContext.rect.top)/height
	};
	vector<GraphObject*> objs(mObjects.begin(), mObjects.end());
	sort(objs.begin(), objs.end(), GraphObject::CompareByZOrder());
	for(vector<GraphObject*>::iterator i = objs.begin(); i!=objs.end(); ++i)
		if((*i)->Visible() && (*i)->Click(p))
			mObjectsClicked.push(*i);
}

GraphDisplay& GraphDisplay::Invalidate()
{
	return InvalidateRect(mContext.rect);
}

GraphDisplay& GraphDisplay::InvalidateRect(const GUI::Rect& rect)
{
	QRegion rgn(
		static_cast<int>(rect.left),
		static_cast<int>(rect.top),
		static_cast<int>(rect.Width()),
		static_cast<int>(rect.Height())
		);
	mInvalidRegion += rgn;
	// On a plain QWidget, we need to call QWidget::update() to make sure
	// the QWidget::repaint() call actually  triggers its paintEvent() rather
	// than blitting from its back buffer.
	// For a QGLWidget, paintEvent() is always triggered by QWidget::update()
	// as well as QWidget::repaint(). So we do not call QWidget::update() for
	// a QGLWidget() to avoid it begin redrawn multiple times.
	if(mpWidget && !mUsingGL)
		mpWidget->update(rgn.translated(-mpWidget->pos()));

	return *this;
}

GUI::Rect GraphDisplay::NormalizedToPixelCoords(const GUI::Rect& inRect) const
{
	float width = mContext.rect.right - mContext.rect.left,
		  height = mContext.rect.bottom - mContext.rect.top;
	GUI::Rect result = {
		mContext.rect.left + width * inRect.left,
		mContext.rect.top + height * inRect.top,
		mContext.rect.right + width * inRect.right,
		mContext.rect.bottom + height * inRect.bottom,
	};
	return result;
}

GUI::Rect GraphDisplay::PixelToNormalizedCoords(const GUI::Rect& inRect) const
{
	const GUI::Rect unitRect = {0,0,1,1};
	if(EmptyRect(mContext.rect))
		return unitRect;
	float width = mContext.rect.right - mContext.rect.left,
		  height = mContext.rect.bottom - mContext.rect.top;
	GUI::Rect result = {
		(inRect.left - mContext.rect.left)/width,
		(inRect.top - mContext.rect.top)/height,
		(inRect.right - mContext.rect.right)/width,
		(inRect.bottom - mContext.rect.bottom)/height
	};

	return result;
}

BitmapImage GraphDisplay::BitmapData(int inWidth, int inHeight) const
{
	int width = inWidth,
		height = inHeight,
		originalWidth = static_cast<int>(mContext.rect.right - mContext.rect.left),
		originalHeight = static_cast<int>(mContext.rect.bottom - mContext.rect.top);
	if(width == 0 && height == 0)
	{
		width = originalWidth;
		height = originalHeight;
	}
	BitmapImage image(width, height);

	if(mOffscreenBmp != NULL)
	{
		BitmapImageFromQPixmap(image, *mOffscreenBmp);
	}else if(mpWidget != NULL)
	{
		BitmapImageFromQPixmap(image, QPixmap::grabWindow(mpWidget->winId()));
	}

	return image;
}

void GraphDisplay::ClearOffscreenBuffer()
{
	if(mOffscreenBmp != NULL)
	{
		delete mOffscreenBmp;
		mOffscreenBmp = NULL;
	}
}

void GraphDisplay::BitmapImageFromQPixmap(BitmapImage& ioImage, const QPixmap& inPixmap)
{
	int width = ioImage.Width(),
		height = ioImage.Height();
	QImage img(inPixmap.scaled(width, height).toImage());
	for(int x = 0; x < width; ++x)
	{
		for(int y = 0; y < height; ++y)
		{
			QColor color = QColor::fromRgba(img.pixel(x,y));
			if(color.alpha() == 0)
				ioImage(x,y) = RGBColor::NullColor;
			else
				ioImage(x,y) = RGBColor(color.red(), color.green(), color.blue());
		}
	}
}

WidgetBase::WidgetBase(GraphDisplay& display)
	:mrGraphDisplay(display),
	 mpWidget(NULL)
{
}

WidgetBase::~WidgetBase()
{
	mrGraphDisplay.mpWidget = NULL;
}

void WidgetBase::SetWidget(QWidget* inpWidget)
{
	mpWidget = inpWidget;
	mpWidget->setAttribute(Qt::WA_NoSystemBackground);
	mpWidget->setAutoFillBackground(false);
	mpWidget->winId();
	mpWidget->setVisible(true);
}

void WidgetBase::OnPaintEvent(QPaintEvent* iopEvent)
{
	QRegion region = iopEvent->region().translated(mpWidget->pos());
	mrGraphDisplay.Paint(&region);
	iopEvent->accept();
}

void WidgetBase::OnMousePressEvent(QMouseEvent* iopEvent)
{
	if(iopEvent->button() == Qt::LeftButton)
	{
		mrGraphDisplay.Click(
			iopEvent->x() + mpWidget->pos().x(),
			iopEvent->y() + mpWidget->pos().y()
			);
		iopEvent->accept();
	}
}